/* mainwindow.cpp
 * Class MainWindow inherits QMainWindow
 * Main GUI logic, buttons, graphs, tables, etc.
 * Created first as soon as the application is started
 * Most of the work is done elsewhere, this class just joins up all the buttons and indicators
 */

#include <QLineEdit>
#include <QCheckBox>
#include <QTableView>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QFileDialog>
#include <QDebug>

#include "registergraph.h"
#include "registermodel.h"
#include "targetcomms.h"
#include "logfiledecoder.h"
#include "pidwidget.h"
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow)
{
    //Initialise threads, models, etc.
    regModel = new RegisterModel(this);
    refreshModel = new RegisterRefreshModel(this);
    thread = new TargetComms(this);
    decodethread = new LogFileDecoder(this);
    graphWindows = QList<RegisterGraph*>();
    m_regList = 0;
    targetFound = false;

    ui->setupUi(this);

    //Give the UI elements names so we can reference them
    ui_lblPort = qFindChild<QLabel*>(this, "lblPort");
    ui_cbConnected = qFindChild<QCheckBox*>(this, "cbConnected");
    ui_tbRegisters = qFindChild<QTableView*>(this, "registerTable");
    ui_mdiArea = qFindChild<QMdiArea*>(this, "mdiArea");
    ui_cbLogStatus = qFindChild<QCheckBox*>(this, "cbLogStatus");
    ui_pidWidget = qFindChild<PIDwidget*>(this, "dwPIDTable");
    ui_refreshWindow = qFindChild<QMdiSubWindow*>(this);
    ui_iInterval = qFindChild<QLineEdit*>(this, "iInterval");
    ui_tbRefresh = qFindChild<QTableView*>(this, "tbRefreshTable");

    //Remove the close button from the Refreshed Registers table subwindow as we don't
    // give users the ability to create another one. window can still be closed by right clicking the title bar
    // only way to prevent that would be to overload the close() method, but I didn't want to have to promote this class
    ui_refreshWindow->setWindowFlags(Qt::CustomizeWindowHint |  Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint);
    ui_refreshWindow->setWindowTitle("Refreshed Registers");

    //Create an initial graph window to get the user started
    RegisterGraph *ui_registerGraph = new RegisterGraph(this);
    ui_mdiArea->addSubWindow(ui_registerGraph, Qt::SubWindow);
    connect(ui_registerGraph, SIGNAL(readNeeded(int)), thread, SLOT(queueRead(int)));
    connect(ui_registerGraph, SIGNAL(destroyed(QObject*)), this, SLOT(closeGraphWindow(QObject*)));
    graphWindows.append(ui_registerGraph);

    //This is an indicator, not an input
    ui_cbConnected->setChecked(false);
    ui_cbConnected->setEnabled(false);

    //Set up drag and drop between the register list and refreshed registers tables
    ui_tbRegisters->setDragEnabled(true);
    ui_tbRegisters->setDropIndicatorShown(true);
    ui_tbRegisters->setDragDropMode(QAbstractItemView::DragOnly);

    ui_tbRefresh->setAcceptDrops(true);
    ui_tbRefresh->setDropIndicatorShown(true);
    ui_tbRefresh->setDragDropMode(QAbstractItemView::DropOnly);

    connect(regModel, SIGNAL(writeNeeded(int,QString)), thread, SLOT(queueWrite(int,QString)));
    connect(refreshModel, SIGNAL(readNeeded(int)), thread, SLOT(queueRead(int)));
    connect(refreshModel, SIGNAL(writeNeeded(int,QString)), thread, SLOT(queueWrite(int,QString)));
    connect(thread, SIGNAL(connectedToTarget(bool,QString)), this, SLOT(targetConnected(bool,QString)));
    connect(thread, SIGNAL(registerListReady(bool)), this, SLOT(registerListFetched(bool)));
    connect(thread, SIGNAL(registerUpdated(RegisterDef)), regModel, SLOT(dataRefreshed(RegisterDef)));
    connect(thread, SIGNAL(registerUpdated(RegisterDef)), refreshModel, SLOT(dataRefreshed(RegisterDef)));
    connect(thread, SIGNAL(registerUpdated(RegisterDef)), this, SLOT(registerUpdated(RegisterDef)));
    connect(thread, SIGNAL(errorMessage(QString)), statusBar(), SLOT(showMessage(QString)));
    connect(thread, SIGNAL(targetLost()), this, SLOT(targetLost()));
    connect(thread, SIGNAL(dumpFinished()), this, SLOT(dumpFinished()));
    connect(decodethread, SIGNAL(decodeFinished()), this, SLOT(decodeComplete()));
    connect(ui_pidWidget, SIGNAL(readNeeded(int)), thread, SLOT(queueRead(int)));
    connect(ui_pidWidget, SIGNAL(writeNeeded(int,QString)), thread, SLOT(queueWrite(int,QString)));
    connect(thread, SIGNAL(registerUpdated(RegisterDef)), ui_pidWidget, SLOT(pidRegisterUpdated(RegisterDef)));
}

MainWindow::~MainWindow()
{
    //This disables logging as it calls the relevant slot
    ui_cbLogStatus->setChecked(false);

    delete ui;
}

void MainWindow::registerListFetched(bool success)
{
    //Once the register list is fetched load up the table views
    // hide system registers and the "flags" and "address" columns
    // then queue a read of all the registers to get their initial values
    if (success && targetFound) {
        updateRtcRegisters();
        ui_tbRegisters->setModel(regModel);
        ui_tbRegisters->setColumnHidden(2,true);
        ui_tbRegisters->setColumnHidden(3,true);
        ui_tbRegisters->resizeColumnsToContents();
        ui_tbRegisters->horizontalHeader()->setStretchLastSection(true);
        ui_tbRegisters->setSelectionBehavior(QAbstractItemView::SelectRows);
        for (register int i=0; i < m_regList->count(); ++i) {
            RegisterDef reg = m_regList->at(i);
            if (reg.system())
                ui_tbRegisters->setRowHidden(i, true);
        }
        ui_tbRefresh->setModel(refreshModel);
        ui_tbRefresh->setColumnHidden(2,true);
        ui_tbRefresh->setColumnHidden(3,true);
        ui_tbRefresh->resizeColumnsToContents();
        ui_tbRefresh->horizontalHeader()->setStretchLastSection(true);
        ui_tbRefresh->setSelectionBehavior(QAbstractItemView::SelectRows);
        ui_pidWidget->loadPIDinfo();
        thread->queueAll();
    }
}

void MainWindow::updateRtcRegisters()
{
    //Set the RTC registers on the device, done as soon as we know where they are
    RegisterDef reg = RegisterDef("RTC_YEAR");
    if (m_regList->contains(reg)) {
        reg = m_regList->at(m_regList->indexOf(reg));
        thread->queueWrite(reg.address(), QString::number(QDateTime::currentDateTime().date().year()));
    }
    reg = RegisterDef("RTC_MONTH");
    if (m_regList->contains(reg)) {
        reg = m_regList->at(m_regList->indexOf(reg));
        thread->queueWrite(reg.address(), QString::number(QDateTime::currentDateTime().date().month()));
    }
    reg = RegisterDef("RTC_DAY");
    if (m_regList->contains(reg)) {
        reg = m_regList->at(m_regList->indexOf(reg));
        thread->queueWrite(reg.address(), QString::number(QDateTime::currentDateTime().date().day()));
    }
    reg = RegisterDef("RTC_HOUR");
    if (m_regList->contains(reg)) {
        reg = m_regList->at(m_regList->indexOf(reg));
        thread->queueWrite(reg.address(), QString::number(QDateTime::currentDateTime().time().hour()));
    }
    reg = RegisterDef("RTC_MINUTE");
    if (m_regList->contains(reg)) {
        reg = m_regList->at(m_regList->indexOf(reg));
        thread->queueWrite(reg.address(), QString::number(QDateTime::currentDateTime().time().minute()));
    }
    reg = RegisterDef("RTC_SECOND");
    if (m_regList->contains(reg)) {
        reg = m_regList->at(m_regList->indexOf(reg));
        thread->queueWrite(reg.address(), QString::number(QDateTime::currentDateTime().time().second()));
    }
}

void MainWindow::targetConnected(bool success, QString port)
{
    //Once the system is connected, send a pointer to the register list to all the classes that need it
    // start the graphs and refreshed registers table
    targetFound = success;
    ui_cbConnected->setChecked(success);
    if (targetFound) {
        ui_lblPort->setText(port);
        statusBar()->showMessage("Successfully connected to target.");
        m_regList = thread->getRegisterList();
        regModel->setRegisterList(m_regList);
        refreshModel->setRegisterList(m_regList);
        refreshModel->start(ui_iInterval->text().toInt());
        foreach (RegisterGraph *graph, graphWindows) {
            graph->setRegisterList(m_regList);
            graph->start();
        }
        ui_pidWidget->setRegisterList(m_regList);
    } else {
        statusBar()->showMessage("Target not found. Check connections.");
    }
}

void MainWindow::targetLost()
{
    //Lost connection to target - pause the graphs and refreshed registers table and notify the user
    targetFound = false;
    ui_cbConnected->setChecked(false);
    ui_lblPort->setText("<target lost>");
    statusBar()->showMessage("Target not found. Check connections.");
    foreach (RegisterGraph *graph, graphWindows) {
        graph->stop();
    }
    refreshModel->pause();
}

void MainWindow::registerUpdated(RegisterDef reg)
{
    //If LOG_CTRL (i.e. harness log status from device) was changed, update the logging status accordingly
    if (reg.name() == "LOG_CTRL")
    {
        reg = m_regList->at(m_regList->indexOf(reg));
        if (reg.iValue() > 0)
            ui_cbLogStatus->setChecked(true);
        else
            ui_cbLogStatus->setChecked(false);
    }
    statusBar()->showMessage("Updating register " + reg.name(), 500);
}

void MainWindow::dumpFinished()
{
    //Finished fetching flash log data - reset the harnesslog and autolog states to their previous states
    // restart the graphs and refresh table
    if (harnessLogStore)
        ui_cbLogStatus->setChecked(true);

    RegisterDef reg = RegisterDef("LOG_AUTO");
    if (m_regList->contains(reg)) {
        reg = m_regList->at(m_regList->indexOf(reg));
        thread->queueWrite(reg.address(), QString::number(autoLogStore));
    }

    foreach (RegisterGraph *graph, graphWindows) {
        graph->start();
    }
    refreshModel->start(ui_iInterval->text().toInt());

    statusBar()->showMessage("Log dump finished");
}

void MainWindow::decodeComplete()
{
    statusBar()->showMessage("Log File conversion finished");
}

void MainWindow::eraseFlashFinished()
{
    //Restart the graphs and refresh table, set the harness and auto log state to previous values
    if (harnessLogStore)
        ui_cbLogStatus->setChecked(true);

    RegisterDef reg = RegisterDef("LOG_AUTO");
    if (m_regList->contains(reg)) {
        reg = m_regList->at(m_regList->indexOf(reg));
        thread->queueWrite(reg.address(), QString::number(autoLogStore));
    }

    foreach (RegisterGraph *graph, graphWindows) {
        graph->start();
    }
    refreshModel->start(ui_iInterval->text().toInt());

    statusBar()->showMessage("Flash erase finished.");
}

void MainWindow::closeGraphWindow(QObject *obj)
{
    //For some reason a qobject_cast<>() to RegisterGraph* and a simple removeAll() doesn't work
    // but this seems to work
    foreach (RegisterGraph* graph, graphWindows) {
        if (obj == graph) {
            graphWindows.removeAll(graph);
        }
    }
}

void MainWindow::on_cbLogStatus_toggled(bool checked)
{
    //Switch flash logging on or off in the device, then notify the comms thread of the change
    RegisterDef reg = RegisterDef("LOG_CTRL");
    if (m_regList->contains(reg)) {
        if (checked) {
            reg = m_regList->at(m_regList->indexOf(reg));
            thread->queueWrite(reg.address(), "1");
        } else {
            reg = m_regList->at(m_regList->indexOf(reg));
            thread->queueWrite(reg.address(), "0");
        }
    }
    thread->setLogStatus(checked);
}

void MainWindow::on_actionNew_Graph_Window_triggered()
{
    //Create a new graph window, and if we already have the register list, join it up and start it
    RegisterGraph *ui_registerGraph = new RegisterGraph(this);
    if (m_regList)
        ui_registerGraph->setRegisterList(m_regList);
    ui_mdiArea->addSubWindow(ui_registerGraph, Qt::SubWindow);
    ui_registerGraph->show();
    connect(ui_registerGraph, SIGNAL(readNeeded(int)), thread, SLOT(queueRead(int)));
    connect(ui_registerGraph, SIGNAL(destroyed(QObject*)), this, SLOT(closeGraphWindow(QObject*)));
    if (targetFound)
        ui_registerGraph->start();
    graphWindows.append(ui_registerGraph); 
}

void MainWindow::on_actionConvertLog_triggered()
{
    QString filename = QFileDialog::getOpenFileName(this, "Convert Log Files", QDir::currentPath(), "Log Files (*.fsl)");
    //If the user cancelled the Open File dialog, the only indication is that the filename is empty
    if (!filename.isEmpty())
        decodethread->requestDecode(filename);
}

void MainWindow::on_actionConnect_triggered()
{
    thread->requestConnect();
}

void MainWindow::on_actionRefresh_triggered()
{
    //Queue a read of all selected registers
    QList<QModelIndex> selectedModel = ui_tbRegisters->selectionModel()->selectedRows(2);
    for (register int i=0; i < selectedModel.count(); i++) {
        int regAddress = selectedModel.at(i).data(Qt::DisplayRole).toInt();
        thread->queueRead(regAddress);
    }
}

void MainWindow::on_actionDump_triggered()
{
    //Save the current states of the harness log and auto log, then stop them
    // stop all graphs and the refresh table, as the system is unresponsive during this time
    foreach (RegisterGraph *graph, graphWindows) {
        graph->stop();
    }
    refreshModel->pause();

    harnessLogStore = ui_cbLogStatus->isChecked();
    if (harnessLogStore)
        ui_cbLogStatus->setChecked(false);

    RegisterDef reg = RegisterDef("LOG_AUTO");
    if (m_regList->contains(reg)) {
        reg = m_regList->at(m_regList->indexOf(reg));
        autoLogStore = reg.iValue();
        thread->queueWrite(reg.address(), "0");
    }
    thread->dumpLogData();
}

void MainWindow::on_iInterval_editingFinished()
{
    //Change the refresh interval of the table model
    refreshModel->pause();
    int interval = ui_iInterval->text().toInt();
    refreshModel->start(interval);
}

void MainWindow::on_bRemove_clicked()
{
    QList<QModelIndex> selectedModel = ui_tbRefresh->selectionModel()->selectedRows(2);
    refreshModel->removeRegisters(selectedModel);
}

void MainWindow::on_actionEraseFlash_triggered()
{
    //Save the current states of the harness log and auto log, then stop them
    // stop all graphs and the refresh table, as the system is unresponsive during this time
    foreach (RegisterGraph *graph, graphWindows) {
        graph->stop();
    }
    refreshModel->pause();

    harnessLogStore = ui_cbLogStatus->isChecked();
    if (harnessLogStore)
        ui_cbLogStatus->setChecked(false);

    //128 is the magic number that erases the flash store on the device
    RegisterDef reg = RegisterDef("LOG_AUTO");
    if (m_regList->contains(reg)) {
        reg = m_regList->at(m_regList->indexOf(reg));
        autoLogStore = reg.iValue();
        thread->queueWrite(reg.address(), "128");
    }

    //Give it 20seconds just to be sure
    QTimer::singleShot(20000, this, SLOT(eraseFlashFinished()));
    statusBar()->showMessage("Erasing flash ...");
}
